﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;

namespace WDCF
{
    /// <summary>
    /// Base class for all formatting classes.
    /// </summary>
    /// <typeparam name="T">A <see cref="FrameworkElement"/> to which to apply conditional formatting.</typeparam>
    public abstract class ConditionalFormatter<T> : IDisposable where T:FrameworkElement
    {
        private Brush conditionalBackgroundColor = Brushes.Red;
        private Brush conditionalTextColor = Brushes.Black;

        private Brush normalBackgroundColor = Brushes.White;
        private Brush normalTextColor = Brushes.Black;

        /// <summary>
        /// Gets or sets the background color for an element if the
        /// condition is fulfilled.
        /// </summary>
        public Brush ConditionalBackgroundColor
        {
            get { return conditionalBackgroundColor; }
            set { conditionalBackgroundColor = value; }
        }

        /// <summary>
        /// Gets or sets the text color for an element if the
        /// condition is fulfilled.
        /// </summary>
        public Brush ConditionalTextColor
        {
            get { return conditionalTextColor; }
            set { conditionalTextColor = value; }
        }

        /// <summary>
        /// Gets or sets the text color for an element if the
        /// condition is not fulfilled.
        /// </summary>
        public Brush NormalTextColor
        {
            get { return normalTextColor; }
            set { normalTextColor = value; }
        }

        /// <summary>
        /// Gets or sets the background color for an element if the
        /// condition is not fulfilled.
        /// </summary>
        public Brush NormalBackgroundColor
        {
            get { return normalBackgroundColor; }
            set { normalBackgroundColor = value; }
        }

        /// <summary>
        /// Gets or sets a List of <see cref="Condition"/>s.
        /// </summary>
        public IList<Condition> Conditions { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConditionalFormatter"/> class.
        /// </summary>
        /// <param name="t"></param>
        /// <param name="conditions"></param>
        protected ConditionalFormatter(
            T t,
            IList<Condition> conditions)
        {
            Conditions = conditions;
            Element = t;
            //styling will be applied when the element was loaded
            Element.Loaded += ElementLoaded;
        }

        protected T Element { get; set; }

        /// <summary>
        /// Generates a <see cref="Style"/> object and applies it to the element.
        /// </summary>
        public abstract void Apply();

        /// <summary>
        /// Gets the <see cref="Style"/> object which shall be replaced with
        /// conditional formatting.
        /// </summary>
        protected abstract Style TargetStyle { get; }

        /// <summary>
        /// Creates or uses an existing <see cref="Style"/> and adds new triggers to it
        /// in order to trigger conditional formatting.
        /// </summary>
        /// <typeparam name="TStyle">The target type for the <see cref="Style"/> object.</typeparam>
        /// <returns></returns>
        protected Style GetStyle<TStyle>()
        {
            var style = TargetStyle ?? new Style(typeof(TStyle));
            style.Triggers.Clear();

            foreach (var condition in Conditions)
            {
                var binding = new Binding(condition.PropertyName);

                switch (condition.PropertyType)
                {
                    case PropertyType.Number:
                        binding.Converter = new NumberToBoolConverter();
                        binding.ConverterParameter = condition.NumericOperator + "|" + condition.CompareValue;
                        break;
                    case PropertyType.String:
                        binding.Converter = new StringToBoolConverter();
                        binding.ConverterParameter = condition.StringOperator + "|" + condition.CompareValue;
                        break;
                }

                var dataTrigger = new DataTrigger { Binding = binding, Value = true };
                dataTrigger.Setters.Add(new Setter(Control.BackgroundProperty, ConditionalBackgroundColor));
                dataTrigger.Setters.Add(new Setter(Control.ForegroundProperty, ConditionalTextColor));
                style.Triggers.Add(dataTrigger);

                dataTrigger = new DataTrigger { Binding = binding, Value = false };
                dataTrigger.Setters.Add(new Setter(Control.BackgroundProperty, NormalBackgroundColor));
                dataTrigger.Setters.Add(new Setter(Control.ForegroundProperty, NormalTextColor));
                style.Triggers.Add(dataTrigger);
            }

            return style;
        }

        /// <summary>
        /// Derived classes may override this method in order to apply 
        /// any steps after the grid has been loaded.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnElementLoaded(RoutedEventArgs e)
        {
            Apply();
        }

        internal PropertyType PropertyType { get; set; }

        #region Implementation of IDisposable

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            if (Element != null)
            {
                Element.Loaded -= ElementLoaded;
            }
        }

        #endregion

        void ElementLoaded(object sender, RoutedEventArgs e)
        {
            OnElementLoaded(e);
        }
    }
}
